﻿#include "audiocontroler.h"




// 构造函数：初始化音频控制器对象的成员变量
AudioControler::AudioControler(QObject *parent) :
    QObject(parent),
    audioInput(nullptr),
    audioDevice(nullptr),
    snowboyDetect(nullptr),
    tts(nullptr)
{

    startNextSpeechTimer.setInterval(500); // 每隔1秒检查一次任务队列
    connect(&startNextSpeechTimer, &QTimer::timeout, this, &AudioControler::startNextSpeech_slot);
    startNextSpeechTimer.start();

}

// 初始化音频控制器，包括音频输入输出和Snowboy检测器的初始化
bool AudioControler::initialize()
{
    int flag;

    // 初始化音频输入设备
    flag = audioInit();
    if(flag == false)
    {
        initialized_state = false;
        emit initialized_sig();  // 发出初始化失败信号
        return false;
    }

    //初始化sherpaNcnn语音识别
    flag = sherpancnnInit();
    if(flag == false)
    {
        initialized_state = false;
        emit initialized_sig();
        return false;
    }

    flag = sherpaonnxInit();
    if(flag == false)
    {
        initialized_state = false;
        emit initialized_sig();
        return false;
    }

    // 读取命令文件
    flag = readCommandsFromFile();
    if(flag == false)
    {
        initialized_state = false;
        emit initialized_sig();
        return false;
    }

    // 初始化Snowboy唤醒词检测器
    flag = snowboyInit();
    if(flag == false)
    {
        initialized_state = false;
        emit initialized_sig();  // 发出初始化失败信号
        return false;
    }

    // 连接音频输入的readyRead信号到Snowboy检测器的中断槽函数
    connect(audioDevice, &QIODevice::readyRead, this, &AudioControler::snowboyInterrupt);

    // 初始化成功
    initialized_state = true;
    emit initialized_sig();  // 发出初始化成功信号
    return true;
}

// 初始化音频输入设备
bool AudioControler::audioInit()
{
    // 设置音频输入格式
    QAudioFormat format;
    format.setSampleRate(16000);  // 设置采样率为16kHz
    format.setChannelCount(1);    // 设置单声道
    format.setSampleSize(16);     // 设置采样大小为16位
    format.setCodec("audio/pcm"); // 设置音频编码为PCM
    format.setSampleType(QAudioFormat::SignedInt); // 设置样本类型为有符号整数
    format.setByteOrder(QAudioFormat::LittleEndian); // 设置字节序为小端模式

    // 获取可用的音频输入设备列表
    QList<QAudioDeviceInfo> devices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
    if(devices.size() == 0)
    {
        qDebug() << "[1.1]Can't find any AudioInputDevice"; // 没有找到音频输入设备
        return false;
    }

    // 查找指定的音频设备
    QAudioDeviceInfo selectedDevice;
    bool findDevice = false;
    for(const auto &device : devices)
    {
        if(device.deviceName() == AUDIO_DEVICE)  // 匹配设备名称
        {
            selectedDevice = device;
            findDevice = true;
            break;
        }
    }
    if(!findDevice)
    {
        qDebug() << "[1.2]Can't find AudioDevice which named:" << AUDIO_DEVICE;  // 没有找到匹配的设备
        return false;
    }

    // 检查设备是否支持设定的音频格式
    if(!selectedDevice.isFormatSupported(format))
    {
        qDebug() << "[1.3]format isn't support audioInputDevice";  // 设备不支持该格式
        return false;
    }

    // 启动音频输入设备
    audioInput = new QAudioInput(selectedDevice, format);
    audioDevice = audioInput->start();
    if(!audioDevice)
    {
        qDebug() << "[1.4]Can't start audioInput";  // 无法启动音频输入设备
        return false;
    }

    // 延时500毫秒初始化音频输出，确保输入设备先完成初始化
    QThread::msleep(500);

    format.setSampleRate(44100);
    audioOutput = new QAudioOutput(selectedDevice, format);

    return true;
}

// 初始化Snowboy检测器
bool AudioControler::snowboyInit()
{
    QString commonRes_path = QString(SNOWBOY_RESOURCES_DIR) + "common.res";  // Snowboy公共资源文件路径
    QString snowboyModel_path = QString(SNOWBOY_RESOURCES_DIR) + "WuYanZhu.pmdl";  // Snowboy唤醒词模型路径

    // 检查资源文件和模型文件是否存在
    if(!QFile(commonRes_path).exists() && !QFile(snowboyModel_path).exists())
    {
        qDebug() << "[1.5]Can't find common.res or xxx.pmdl,please check file_path";  // 未找到资源文件或模型文件
        return false;
    }

    // 创建Snowboy检测器实例并设置参数
    snowboyDetect = new snowboy::SnowboyDetect(commonRes_path.toStdString(), snowboyModel_path.toStdString());
    if(!snowboyDetect)
    {
        qDebug() << "[1.6]failed to create SnowboyDetect";
        return false;
    }

    snowboyDetect->SetSensitivity("0.5");  // 设置检测灵敏度
    snowboyDetect->SetAudioGain(1.0);  // 设置音频增益

    return true;
}

// Snowboy检测器的中断处理函数
void AudioControler::snowboyInterrupt()
{

    // 读取音频输入数据
    audioBuffer = audioDevice->readAll();

    // 运行Snowboy检测算法
    int result = snowboyDetect->RunDetection((int16_t*)audioBuffer.data(), audioBuffer.size() / 2);
    if(result > 0)  // 如果检测到唤醒词
    {
        qDebug() << "snowboyInterrupt";

        stop_snowboyInterrupt();

        // 播放提示音
        QString audioPath = QString(AUDIO_DIR) + "ding.wav";
        playAudio(audioPath);


        audioDeviceBusy = true;
        audioDevice = audioInput->start();

        sherpaNcnnRecognition();

        audioInput->stop();
        audioDeviceBusy = false;

        start_snowboyInterrupt();
    }
}

// 获取音频控制器初始化状态
bool AudioControler::getAudioControlerInit_state()
{
    return initialized_state;
}

// 播放音频文件
bool AudioControler::playAudio(QString &audioPath)
{
    audioDeviceBusy = true;

    // 检查音频文件是否存在
    if (!QFile(audioPath).exists())
    {
        qDebug() << "[1.7] Can't find audioPath named:" << audioPath;  // 没有找到指定的音频文件
        audioDeviceBusy = false;
        return false;
    }

    // 启动音频输出设备
    audioDevice = audioOutput->start();

    // 获取音频输出设备的周期大小（每次可以写入的字节数）
    int size = audioOutput->periodSize();
    char* buf = new char[size];  // 为缓冲区分配内存

    // 打开音频文件
    FILE* fp = fopen(audioPath.toStdString().c_str(), "rb");
    while (!feof(fp)) {  // 循环读取文件
        // 如果输出设备的空闲字节数小于周期大小，则等待
        if (audioOutput->bytesFree() < size) {
            QThread::msleep(1);  // 暂停 1 毫秒
            continue;
        }

        // 从文件中读取数据到缓冲区
        int len = fread(buf, 1, size, fp);
        if (len <= 0) {  // 文件已读取完毕
            break;
        }

        // 将数据写入音频输出设备
        audioDevice->write(buf, len);
    }

    // 停止音频输出
    audioOutput->stop();

    // 关闭音频文件
    fclose(fp);
    delete[] buf;  // 释放缓冲区内存

    audioDeviceBusy = false;
    return true;
}

// 初始化 Sherpa-Ncnn 识别器
bool AudioControler::sherpancnnInit()
{
    SherpaNcnnRecognizerConfig config;
    memset(&config, 0, sizeof(SherpaNcnnRecognizerConfig));  // 初始化配置

    // 定义模型文件路径
    QString tokensTxt_path = QString(SHERPANCNN_MODEL_DIR) + "tokens.txt";
    QString decoderBin_path = QString(SHERPANCNN_MODEL_DIR) + "decoder_jit_trace-pnnx.ncnn.bin";
    QString decoderParam_path = QString(SHERPANCNN_MODEL_DIR) + "decoder_jit_trace-pnnx.ncnn.param";
    QString encoderBin_path = QString(SHERPANCNN_MODEL_DIR) + "encoder_jit_trace-pnnx.ncnn.bin";
    QString encoderParam_path = QString(SHERPANCNN_MODEL_DIR) + "encoder_jit_trace-pnnx.ncnn.param";
    QString joinerBin_path = QString(SHERPANCNN_MODEL_DIR) + "joiner_jit_trace-pnnx.ncnn.bin";
    QString joinerParam_path = QString(SHERPANCNN_MODEL_DIR) + "joiner_jit_trace-pnnx.ncnn.param";

    // 检查模型文件是否存在
    if (!QFile(tokensTxt_path).exists() ||
        !QFile(decoderBin_path).exists() ||
        !QFile(decoderParam_path).exists() ||
        !QFile(encoderBin_path).exists() ||
        !QFile(encoderParam_path).exists() ||
        !QFile(joinerBin_path).exists() ||
        !QFile(joinerParam_path).exists())
    {
        qDebug() << "[1.8] Can't find required model files, please check file paths.";
        return false;
    }

    // 将 QString 转换为 std::string
    std::string str_tokensTxt = tokensTxt_path.toStdString();
    std::string str_decoderBin = decoderBin_path.toStdString();
    std::string str_decoderParam = decoderParam_path.toStdString();
    std::string str_encoderBin = encoderBin_path.toStdString();
    std::string str_encoderParam = encoderParam_path.toStdString();
    std::string str_joinerBin = joinerBin_path.toStdString();
    std::string str_joinerParam = joinerParam_path.toStdString();

    // 配置识别器参数
    config.model_config.tokens = str_tokensTxt.c_str();
    config.model_config.decoder_bin = str_decoderBin.c_str();
    config.model_config.decoder_param = str_decoderParam.c_str();
    config.model_config.encoder_bin = str_encoderBin.c_str();
    config.model_config.encoder_param = str_encoderParam.c_str();
    config.model_config.joiner_bin = str_joinerBin.c_str();
    config.model_config.joiner_param = str_joinerParam.c_str();

    // 设置其他配置项
    config.model_config.num_threads = 4;  // 线程数
    config.model_config.use_vulkan_compute = 0;  // 不使用 Vulkan 计算
    config.decoder_config.decoding_method = "greedy_search";  // 解码方法
    config.decoder_config.num_active_paths = 4;  // 激活路径数
    config.enable_endpoint = 1;  // 启用端点检测
    config.rule1_min_trailing_silence = 2.4;  // 最小尾部静音时长
    config.rule2_min_trailing_silence = 1.2;  // 第二条规则最小尾部静音时长
    config.rule3_min_utterance_length = 300;  // 最小发声长度
    config.feat_config.sampling_rate = 16000;  // 采样率
    config.feat_config.feature_dim = 80;  // 特征维度

    // 创建识别器
    recognizer = CreateRecognizer(&config);
    if (!recognizer)
    {
        qDebug() << "[1.9] Failed to create SherpaNcnnRecognizer.";
        return false;
    }

    // 创建流
    stream = CreateStream(recognizer);
    if (!stream)
    {
        qDebug() << "[1.10] Failed to create SherpaNcnnStream.";
        return false;
    }

    return true;
}

// 启动snowboy中断检测
void AudioControler::start_snowboyInterrupt()
{
    // 重新启动音频输入
    audioDevice = audioInput->start();
    connect(audioDevice, &QIODevice::readyRead, this, &AudioControler::snowboyInterrupt);  // 重新连接信号
}

// 停止snowboy中断检测
void AudioControler::stop_snowboyInterrupt()
{
    disconnect(audioDevice, &QIODevice::readyRead, this, &AudioControler::snowboyInterrupt);  // 断开音频输入信号
    audioInput->stop();  // 停止音频输入
}

// Sherpa-Ncnn 识别过程
void AudioControler::sherpaNcnnRecognition()
{
    const int time = 20;  // 超时时间
    int remainingTime = time;  // 剩余时间

    audioBuffer.resize(0);  // 清空音频缓冲区

    while (true)
    {
        // 读取音频数据
        audioBuffer = audioDevice->readAll();
        if (!audioBuffer.isEmpty())
        {
            const int N = audioBuffer.size() / sizeof(int16_t);  // 获取读取到的样本数
            QVector<float> samples(N);  // 创建样本数组
            const int16_t* buffer = reinterpret_cast<const int16_t*>(audioBuffer.constData());

            // 将 int16_t 的音频样本转换为 float
            for (int i = 0; i < N; ++i)
            {
                samples[i] = buffer[i] / 32768.0f;  // 归一化
            }

            // 传递音频数据到识别器
            AcceptWaveform(stream, 16000, samples.constData(), samples.size());

            // 检查识别器是否准备好解码
            if (IsReady(recognizer, stream))
            {
                Decode(recognizer, stream);
                SherpaNcnnResult* result = GetResult(recognizer, stream);

                // 检查是否到达端点（用户是否停止说话）
                if (IsEndpoint(recognizer, stream))
                {
                    if (strlen(result->text) > 0)
                    {
                        remainingTime = time;  // 重置剩余时间
                        qDebug() << "reset time";

                        // 处理识别的完整命令
                        qDebug() << "Recognized command:" << result->text;

                        //
                        string command = result->text;
                        string command_matched = matchCommand(command);
                        if(command_matched == "InvalidCommand")
                        {
                            qDebug() << "InvalidCommand";

                            audioInput->stop();
                            QString audio_path = QString(AUDIO_DIR)+"audio1.wav";
                            playAudio(audio_path);
                            audioDevice = audioInput->start();

                        }
                        else
                        {
                            processCommand(command_matched);
                        }
                        // 清除当前流，准备下一段音频
                        Reset(recognizer, stream);
                    }

                    remainingTime--;  // 减少剩余时间
                    qDebug() << "RemainingTime:" << remainingTime;
                    if (remainingTime == 0)
                    {
                        Reset(recognizer, stream);  // 重置识别流
                        audioInput->stop();  // 停止音频输入

                        // 播放提示音
                        QString audioPath = QString(AUDIO_DIR) + "dong.wav";
                        playAudio(audioPath);  // 播放音频

                        audioDevice = audioInput->start();  // 重新启动音频输入

                        qDebug() << "timeout, return";
                        return;
                    }
                }
                DestroyResult(result);  // 释放识别结果
            }
        }
    }
}


// 读取命令文件
bool AudioControler::readCommandsFromFile()
{
    QString commandsTxt_path = QString(SHERPANCNN_RESOURCES_DIR)+"commands.txt";
    if(!QFile(commandsTxt_path).exists())
    {
        qDebug() << "[1.11]Can't find commands.txt ,please check file paths";
        return false;
    }

    ifstream file(commandsTxt_path.toStdString());
    string line;
    while (getline(file, line))
    {
        if (!line.empty())
        {
            commands.push_back(line);
        }
    }
    file.close();
    return true;
}

// 计算相似度
double AudioControler::calculateSimilarity(const string& userCommand, const string& command)
{
    unordered_map<char, int> charCount;

    // 统计 userCommand 中的字符
    for (char c : userCommand)
    {
        charCount[c]++;
    }

    int matchingCount = 0;

    // 统计 command 中与 userCommand 相同的字符数量
    for (char c : command)
    {
        if (charCount[c] > 0)
        {
            matchingCount++;
            charCount[c]--;  // 确保每个字符只计算一次
        }
    }

    // 根据匹配的字符数量计算相似度
    return static_cast<double>(matchingCount) / max(userCommand.size(), command.size());
}

// 输入字符串并输出匹配的命令
string AudioControler::matchCommand(const string& userCommand)
{
    string bestMatch;
    double maxSimilarity = 0.0;

    for (const auto& command : commands)
    {
        double sim = calculateSimilarity(userCommand, command);
        cout << "命令: " << command << " 相似度: " << sim << endl;

        if (sim > maxSimilarity)
        {
            maxSimilarity = sim;
            bestMatch = command;
        }
    }

    if (maxSimilarity < 0.6)
    {  // 设置阈值为60%
        cout << "无效命令" << endl;
        return "InvalidCommand";

    } else
    {
        cout << "最佳匹配: " << bestMatch << " 相似度: " << maxSimilarity << endl;
        return bestMatch;
    }
}

void AudioControler::processCommand(const string &command_matched)
{
    qDebug() << "识别到命令："<< QString::fromStdString(command_matched);


    if(command_matched == "启动在线导航")
    {
        audioInput->stop();

        QString audio_path = QString(AUDIO_DIR)+"audio2.wav";
        playAudio(audio_path);

        audioDevice = audioInput->start();
    }
}






void AudioControler::getSpeechTeskFromController_slot(QString str, int priority)
{
        SpeechTask task{str, priority};  // 创建语音任务
        speechQueue.push(task);          // 添加任务到管理器
        qDebug() << "Added speech task with content:" << str << "and priority:" << priority;
}


void AudioControler::startNextSpeech_slot()
{
    // 检查队列是否为空以及语音模块是否处于播放状态
    if (!speechQueue.empty() && audioDeviceBusy==false)
    {
        SpeechTask nextTask = speechQueue.top(); // 获取队列顶部的任务
        speechQueue.pop(); // 移除已获取的任务


        stop_snowboyInterrupt();
        audioDeviceBusy = true;
        // 播放音频，根据 nextTask.content 的内容决定播放的音频文件
        if (nextTask.content == ".注意左前方.") // 如果内容为 "注意左前方"
        {
            QString audio_path = QString(AUDIO_DIR) + "left.wav";
            playAudio(audio_path); // 播放左前方警告音频
        }
        else if (nextTask.content == ".注意前方.") // 如果内容为 "注意前方"
        {
            QString audio_path = QString(AUDIO_DIR) + "front.wav";
            playAudio(audio_path); // 播放前方警告音频
        }
        else if (nextTask.content == ".注意右前方.") // 如果内容为 "注意右前方"
        {
            QString audio_path = QString(AUDIO_DIR) + "right.wav";
            playAudio(audio_path); // 播放右前方警告音频
        }
        audioDeviceBusy = false;
        start_snowboyInterrupt();
    }
}


bool AudioControler::sherpaonnxInit()
{
    // 配置 SherpaOnnxOfflineTtsConfig 参数
    SherpaOnnxOfflineTtsConfig config;
    memset(&config,0,sizeof(SherpaOnnxOfflineTtsConfig));

    // 定义模型文件路径
    QString modelOnnx_path = QString(SHERPAONNX_MODEL_DIR) + "model.onnx";
    QString tokensTxt_path = QString(SHERPAONNX_MODEL_DIR) + "tokens.txt";
    QString lexiconTxt_path = QString(SHERPAONNX_MODEL_DIR) + "lexicon.txt";
    QString dict_dir = QString(SHERPAONNX_MODEL_DIR) + "dict";


    // 检查模型文件是否存在
    if (!QFile(modelOnnx_path).exists() ||
        !QFile(tokensTxt_path).exists() ||
        !QFile(lexiconTxt_path).exists() ||
        !QDir(dict_dir).exists())
    {
        qDebug() << "[1.12] Can't find required model files, please check file paths.";
        return false;
    }

    // 将 QString 转换为 std::string
    std::string str_modelOnnx = modelOnnx_path.toStdString();
    std::string str_tokensTxt = tokensTxt_path.toStdString();
    std::string str_lexiconTxt = lexiconTxt_path.toStdString();
    std::string str_dict = dict_dir.toStdString();

    config.model.vits.model = str_modelOnnx.c_str(); //嵌入式设备可用model.int8.onnx
    config.model.vits.lexicon = str_lexiconTxt.c_str();
    config.model.vits.tokens = str_tokensTxt.c_str();
    config.model.vits.dict_dir = str_dict.c_str();
    config.model.vits.noise_scale = 0.667;
    config.model.vits.noise_scale_w = 0.8;
    config.model.vits.length_scale = 1.0;
    config.model.num_threads = 4;
    config.model.provider = "cpu";
    config.model.debug = 0;
    //config.rule_fsts = ;
    //config.max_num_sentences = ;
    //config.model.vits.data_dir = ;

    tts = SherpaOnnxCreateOfflineTts(&config);
    if(!tts)
    {
        qDebug() << "[1.13]Failed to create SherpaOnnxOfflineTts.";
        return false;
    }


    return true;
}



void AudioControler::generateAudio(const QString &text, const QString &outputFile)
{
//    int32_t sampleRate = SherpaOnnxOfflineTtsSampleRate(tts);
//    qDebug() << "当前采样率:" << sampleRate;

    if(!tts)
    {
        qDebug() << "generate fail,loadModel first";
        return;
    }
    int32_t sid = 0;
    const SherpaOnnxGeneratedAudio *audio = SherpaOnnxOfflineTtsGenerate(tts,text.toUtf8().data(),sid,0.8);
    if(!audio)
    {
        qDebug() << "generate audio failed";
        return;
    }
    SherpaOnnxWriteWave(audio->samples,audio->n,audio->sample_rate,outputFile.toStdString().c_str());
    SherpaOnnxDestroyOfflineTtsGeneratedAudio(audio);
}
